#include <glib.h>
#include <stdlib.h>
#include <sys/mman.h>

#include "private.h"
#include "driver/driver_wrapper.h"
#include "arch/riscv64.h"

#ifndef ENABLE_KVM_SEE
status_t v2p_riscv64(vmi_instance_t vmi, addr_t npt, page_mode_t npm,
                     addr_t pt, addr_t vaddr, page_info_t *info) {
    errprint(">>> luowu. [%s]\n", __func__);
    return VMI_FAILURE;
}

GSList* get_va_pages_riscv64(vmi_instance_t UNUSED(vmi), addr_t UNUSED(dtb)) {
    errprint(">>> luowu. [%s]\n", __func__);
    return VMI_FAILURE;
}

GSList* get_pages_riscv64(vmi_instance_t vmi, addr_t UNUSED(npt), page_mode_t UNUSED(npm), addr_t pt) {
    errprint(">>> luowu. [%s]\n", __func__);
    return VMI_FAILURE;
}
#else

#define GET_VA_VPN4(x)                      ((x & ((1 << 57) - 1)) >> 48)
#define GET_VA_VPN3(x)                      ((x & ((1 << 48) - 1)) >> 39)
#define GET_VA_VPN2(x)                      ((x & ((1 << 39) - 1)) >> 30)
#define GET_VA_VPN1(x)                      ((x & ((1 << 30) - 1)) >> 21)
#define GET_VA_VPN0(x)                      ((x & ((1 << 21) - 1)) >> 12)
#define GET_VA_OFFSET(x)                    (x & ((1 << 12) - 1))

#define GET_PAGE_SIZE_IN_LEVEL(level)       (1 << (12 + 9 * level))
#define GET_VA_OFFSET_IN_LEVEL(va, level)   (va & ((1 << (12 + 9 * level)) - 1))

#define GET_VA_VPN_IN_LEVEL(va, level)      ((va & (((addr_t)1 << (21 + 9 * level)) - 1)) >> (12 + 9 * level))

#define GET_PPN_FROM_PTE(x)                 (((x >> 10) & (((addr_t)1 << 44) - 1)) << 12)
#define GET_V_BIT_FROM_PTE(x)               (x & 0x1)
#define GET_R_BIT_FROM_PTE(x)               (x & 0x2)
#define GET_W_BIT_FROM_PTE(x)               (x & 0x4)
#define GET_X_BIT_FROM_PTE(x)               (x & 0x8)

#define PTE_SIZE                8

status_t v2p_riscv64(vmi_instance_t vmi, addr_t npt, page_mode_t npm,
                     addr_t pt, addr_t vaddr, page_info_t *info) {
    status_t status = VMI_FAILURE;
    int level = 5; // SV57
    int i = 0;
    addr_t pd = pt;

    dbprint(VMI_DEBUG_PTLOOKUP, "--RISCV64 SV57 PTLookup: vaddr = 0x%.16"PRIx64", pt = 0x%.16"PRIx64"\n", vaddr, pt);
    while(level-- > 0) {
        addr_t vpn = GET_VA_VPN_IN_LEVEL(vaddr, level);
        addr_t pte = pd + vpn * PTE_SIZE;
        uint64_t pte_data = 0;
        uint64_t ppn = 0;

        dbprint(VMI_DEBUG_PTLOOKUP, "--search for level %d. pd: 0x%lx. vpn: 0x%lx. pte_addr: 0x%lx\n", level, pd, vpn, pte);

        if (VMI_FAILURE == vmi_read_64_pa(vmi, pte, &pte_data)) {
            errprint(">>> luowu. [%s] cannot read PA: 0x%lx\n", __func__, pte);
            return VMI_FAILURE;
        }

        ppn = GET_PPN_FROM_PTE(pte_data);
        if (GET_V_BIT_FROM_PTE(pte_data) != 1) {
            errprint(">>> luowu. [%s] bad pte data: 0x%lx in pa 0x%lx\n", __func__, pte_data, pte);
            return VMI_FAILURE;
        }

        if (level == 0) {
            // the last layer
            info->size = VMI_PS_4KB;
            info->paddr = ppn + GET_VA_OFFSET(vaddr);
            dbprint(VMI_DEBUG_PTLOOKUP, "--Found PA in level %d. pa: 0x%lx, size: 0x%x\n", level, info->paddr, info->size);
            return VMI_SUCCESS;
        }

        dbprint(VMI_DEBUG_PTLOOKUP, "-- ptedata=0x%lx ppn=0x%lx\n", pte_data, ppn);

        if (GET_R_BIT_FROM_PTE(pte_data) != 0 || GET_X_BIT_FROM_PTE(pte_data) != 0) {
            // found the leaf node
            info->size = GET_PAGE_SIZE_IN_LEVEL(level);
            info->paddr = ppn + GET_VA_OFFSET_IN_LEVEL(vaddr, level);
            dbprint(VMI_DEBUG_PTLOOKUP, "--Found PA in level %d. pa: 0x%lx, size: 0x%x\n", level, info->paddr, info->size);
            return VMI_SUCCESS;
        }

        pd = ppn;
    }
    dbprint(VMI_DEBUG_PTLOOKUP, "--Not Found a leaf PT\n", level);

    return VMI_FAILURE;
}

GSList* get_va_pages_riscv64(vmi_instance_t UNUSED(vmi), addr_t UNUSED(dtb)) {
    errprint(">>> luowu. [%s] Not Implemented\n", __func__);
    return NULL;
}

GSList* get_pages_riscv64(vmi_instance_t vmi, addr_t UNUSED(npt), page_mode_t UNUSED(npm), addr_t pt) {
    errprint(">>> luowu. [%s] Not Implemented\n", __func__);
    return NULL;
}
#endif